package cn.sdc.sfw.util;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import cn.sdc.sfw.log.Logger;

public class ReflectUtil {

	protected transient final static Logger log = Logger.getLogger(ReflectUtil.class);

	/**
	 * find the special field by the supplied field name from the class.
	 * 
	 * @param clazz
	 * @param name
	 * @return
	 */
	public static Field findField(final Class clazz, final String name) {
		Class searchType = clazz;
		while (!Object.class.equals(searchType) && searchType != null) {
			final Field[] fields = searchType.getDeclaredFields();
			for (int i = 0; i < fields.length; i++) {
				Field field = fields[i];
				if (name.equals(field.getName())) {
					return field;
				}
			}
			searchType = searchType.getSuperclass();
		}
		return null;
	}

	/**
	 * find the special method by the supplied method name and param types from the class.
	 * 
	 * @param clazz
	 * @param name
	 * @param paramTypes
	 * @return
	 */
	public static Method findMethod(Class clazz, String name, Class[] paramTypes) {
		Class searchType = clazz;
		while (!Object.class.equals(searchType) && searchType != null) {
			Method[] methods = (searchType.isInterface() ? searchType
					.getMethods() : searchType.getDeclaredMethods());
			for (int i = 0; i < methods.length; i++) {
				Method method = methods[i];
				if (name.equals(method.getName())
						&& isSameParamterTypes(paramTypes, method)) {
					return method;
				}
			}
			searchType = searchType.getSuperclass();
		}
		return null;
	}
	
	/**
	 * find the special method by the supplied method name from the class.
	 * 
	 * @param clazz
	 * @param name
	 * @return
	 */
	public static Method findMethod(Class clazz, String name) {
		Class searchType = clazz;
		while (!Object.class.equals(searchType) && searchType != null) {
			Method[] methods = (searchType.isInterface() ? searchType
					.getMethods() : searchType.getDeclaredMethods());
			for (int i = 0; i < methods.length; i++) {
				Method method = methods[i];
				if (name.equals(method.getName())) {
					return method;
				}
			}
			searchType = searchType.getSuperclass();
		}
		return null;
	}

	private static boolean isSameParamterTypes(Class[] paramTypes, Method method) {
		try {
			Class[] methodTypes = method.getParameterTypes();
			if(paramTypes == null && methodTypes == null){
				return true;
			}else if(paramTypes == null || methodTypes == null){
				return false;
			}else if(paramTypes.length != methodTypes.length){
				return false;
			}
			for(int i = 0; i < paramTypes.length; i++){
				if(methodTypes[i].isPrimitive()){
					if("char".equals(methodTypes[i].getName()) && !("java.lang.Character".equals(paramTypes[i].getName())
							|| "char".equals(paramTypes[i].getName()))){
						return false;
					}
					if("boolean".equals(methodTypes[i].getName()) && !("java.lang.Boolean".equals(paramTypes[i].getName())
							|| "boolean".equals(paramTypes[i].getName()))){
						return false;
					}
					if("byte".equals(methodTypes[i].getName()) && !("java.lang.Byte".equals(paramTypes[i].getName())
							|| "byte".equals(paramTypes[i].getName()))){
						return false;
					}
					if("short".equals(methodTypes[i].getName()) && !("java.lang.Short".equals(paramTypes[i].getName())
							|| "short".equals(paramTypes[i].getName()))){
						return false;
					}
					if("int".equals(methodTypes[i].getName()) && !("java.lang.Integer".equals(paramTypes[i].getName())
							|| "int".equals(paramTypes[i].getName()))){
						return false;
					}
					if("long".equals(methodTypes[i].getName()) && !("java.lang.Long".equals(paramTypes[i].getName())
							|| "long".equals(paramTypes[i].getName()))){
						return false;
					}
					if("float".equals(methodTypes[i].getName()) && !("java.lang.Float".equals(paramTypes[i].getName())
							|| "float".equals(paramTypes[i].getName()))){
						return false;
					}
					if("double".equals(methodTypes[i].getName()) && !("java.lang.Double".equals(paramTypes[i].getName())
							|| "double".equals(paramTypes[i].getName()))){
						return false;
					}
				}else if(!methodTypes[i].getName().equals(paramTypes[i].getName())){
					return false;
				}
			} 			
		}catch (Exception e) {
			return false;
		}
		
		return true;
	}
	
	/**
	 * get the one object's field string value by the fieldName.
	 * 
	 * @param obj
	 * @param fieldName
	 * @return
	 */

	public static String getFieldStrValue(Object obj, String fieldName) {
		return getFieldValue(obj, fieldName).toString();
	}

	/**
	 * get the special object's filed value by this object's getMethod.
	 * 
	 * @param obj
	 * @param fieldName
	 * @return
	 */
	public static Object getFieldValue(Object obj, String fieldName) {
		
		String getMethodName = "get" + fieldName.substring(0, 1).toUpperCase()
				+ fieldName.substring(1);
		
		//Method method = findMethod(obj.getClass(), getMethodName, new Class[0]);
		Method method = findMethod(obj.getClass(), getMethodName);
		if (method == null) {
			String isMethodName = "is" + fieldName.substring(0, 1).toUpperCase()
                    + fieldName.substring(1);
			//method = findMethod(obj.getClass(), isMethodName, new Class[0]);
			method = findMethod(obj.getClass(), isMethodName);
			if(method == null){
				log.error("Can not find method: " + getMethodName + " at class " + obj.getClass().getName());
			}
		}
		Object result = null;
		try {
			result = method.invoke(obj);
		} catch (Exception ex) {
			handleReflectionException(ex);
			log.error("Unexpected reflection exception - "
					+ ex.getClass().getName() + ": " + ex.getMessage());
		}
		return result;
	}

	/**
	 * set the field value for one object by the field name.
	 * This field must have a setMethod.
	 * 
	 * @param obj
	 * @param fieldName
	 * @param value
	 */
	public static void setFieldValue(Object obj, String fieldName, Object value) {

		String setMethodName = "set" + fieldName.substring(0, 1).toUpperCase()
				+ fieldName.substring(1);
		//Method method = findMethod(obj.getClass(), setMethodName, new Class[]{value.getClass()});
		Method method = findMethod(obj.getClass(), setMethodName);
		if (method == null) {
			log.error("Can not find method: " + setMethodName + " at class "
					+ obj.getClass().getName());
		}
		try {
			method.invoke(obj, value);
		} catch (Exception ex) {
			handleReflectionException(ex);
			log.error("Unexpected reflection exception - "
					+ ex.getClass().getName() + ": " + ex.getMessage());
		}
	}

	/***************************************************************************
	 **** The following mdethod is the exception handle method. Only used in this class. 
	 **************************************************************************/

	private static void handleInvocationTargetException(
			InvocationTargetException ex) {
		rethrowRuntimeException(ex.getTargetException());
	}

	private static void handleReflectionException(Exception ex) {
		if (ex instanceof NullPointerException || ex instanceof NoSuchMethodException) {
			throw new IllegalStateException("Method not found: "
					+ ex.getMessage());
		}
		if (ex instanceof IllegalAccessException) {
			throw new IllegalStateException("Could not access method: "
					+ ex.getMessage());
		}
		if (ex instanceof InvocationTargetException) {
			handleInvocationTargetException((InvocationTargetException) ex);
		}
		if (ex instanceof RuntimeException) {
			throw (RuntimeException) ex;
		}
		handleUnexpectedException(ex);
	}

	private static void handleUnexpectedException(Throwable ex) {
		IllegalStateException isex = new IllegalStateException(
				"Unexpected exception thrown");
		isex.initCause(ex);
		throw isex;
	}

	private static void rethrowRuntimeException(Throwable ex) {
		if (ex instanceof RuntimeException) {
			throw (RuntimeException) ex;
		}
		if (ex instanceof Error) {
			throw (Error) ex;
		}
		handleUnexpectedException(ex);
	}

}
